// SPDX-License-Identifier: GPL-2.0

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/security.h>
#include <linux/init.h>
#include <linux/integrity.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/seq_file.h>
#include <linux/list.h>
#include <linux/vmalloc.h>

#include "dim_core_fs.h"
#include "dim_core.h"
#include "dim_core_measure.h"
#include "dim_core_measure_log.h"
#include "dim_core_policy.h"
#include "dim_core_status.h"

#define DIM_FS_TMP_BUF_SIZE	256

static bool is_digit(const char *s, int len)
{
	int i;

	if (len <= 0)
		return false;

	for (i = 0; i < len; i++) {
		if (s[i] < '0' || s[i] > '9')
			return false;
	}

	return true;
}

/*
 * runtime_status interface
 */
static ssize_t dim_runtime_status_read(struct file *filp,
				       char __user *buf,
				       size_t count,
				       loff_t *ppos)
{
	char tmpbuf[DIM_FS_TMP_BUF_SIZE];
	ssize_t len;

	len = scnprintf(tmpbuf,
			DIM_FS_TMP_BUF_SIZE,
			"Dim running status: %s\n",
			dim_get_running_status_str());

	return simple_read_from_buffer(buf, count, ppos, tmpbuf, len);
}

static const struct file_operations dim_runtime_status_ops = {
	.read = dim_runtime_status_read,
	.llseek = generic_file_llseek,
};

/*
 * measure interface
 */
static ssize_t dim_measure_write(struct file *file,
				 const char __user *buf,
				 size_t count, loff_t *ppos)
{
	char *buf_copy = NULL;
	int val, ret;

	if (*ppos != 0 || count > 2)
		return -EINVAL;

	buf_copy = memdup_user_nul(buf, count);
	if (IS_ERR(buf_copy))
		return PTR_ERR(buf_copy);

	if (sscanf(buf_copy, "%d", &val) != 1 || val != 1) {
		ret = -EINVAL;
		goto out;
	}

	ret = dim_measure_all();
	if (ret < 0)
		goto out;

	ret = count;
out:
	kfree(buf_copy);
	return ret;
}

static const struct file_operations dim_measure_ops = {
	.write = dim_measure_write,
	.llseek = generic_file_llseek,
};

/*
 * ascii_runtime_measurements interface
 */
static void *dim_ascii_runtime_measurements_read_start(struct seq_file *m, loff_t *pos)
{
	struct list_head *p = NULL;
	loff_t l = *pos;

	p = dim_get_measure_log_first();
	while (l--)
		p = dim_get_measure_log_next(p);

	return p;
}

static void *dim_ascii_runtime_measurements_read_next(struct seq_file *m, void *v, loff_t *pos)
{
	(*pos)++;
	return dim_get_measure_log_next((struct list_head *)v);
}

static void dim_ascii_runtime_measurements_read_stop(struct seq_file *m, void *v)
{
}

int dim_ascii_runtime_measurements_read_show(struct seq_file *m, void *v)
{
	char *buf = NULL;
	int size = PATH_MAX + MAX_HEX_SIZE + MAX_HEX_SIZE + NAME_MAX;

	buf = vmalloc(size);
	if (buf == NULL)
		return -ENOMEM;

	if (dim_show_measure_log_by_node((struct list_head *)v,
		buf, size) != NULL)
		seq_printf(m, "%s\n", buf);

	vfree(buf);
	return 0;
}

static const struct seq_operations dim_ascii_runtime_measurements_read_seqops = {
	.start = dim_ascii_runtime_measurements_read_start,
	.next = dim_ascii_runtime_measurements_read_next,
	.stop = dim_ascii_runtime_measurements_read_stop,
	.show = dim_ascii_runtime_measurements_read_show
};

static int dim_ascii_measurements_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &dim_ascii_runtime_measurements_read_seqops);
}

static const struct file_operations dim_ascii_measurements_ops = {
	.open = dim_ascii_measurements_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = seq_release,
};

/*
 * interval interface
 */
static ssize_t dim_interval_read(struct file *file,
				 char __user *buf,
				 size_t count,
				 loff_t *ppos)
{
	char tmpbuf[DIM_FS_TMP_BUF_SIZE];
	ssize_t len;

	len = scnprintf(tmpbuf, DIM_FS_TMP_BUF_SIZE, "%lu\n",
		dim_measure_timer_get_intervel());

	return simple_read_from_buffer(buf, count, ppos, tmpbuf, len);
}

static ssize_t dim_interval_write(struct file *file,
				 const char __user *buf,
				 size_t count, loff_t *ppos)
{
	char *buf_copy = NULL;
	unsigned long val;
	int ret;

	if (*ppos != 0 || count <= 0 || count > 7)
		return -EINVAL;

	buf_copy = memdup_user_nul(buf, count);
	if (IS_ERR(buf_copy))
		return PTR_ERR(buf_copy);

	if (!is_digit(buf_copy,
		buf_copy[count - 1] == '\n' ? count - 1 : count)) {
		ret = -EINVAL;
		goto out;
	}

	if (sscanf(buf_copy, "%lu", &val) != 1) {
		ret = -EINVAL;
		goto out;
	}

	ret = dim_measure_timer_set_intervel(val);
	if (ret < 0)
		goto out;

	ret = count;
out:
	kfree(buf_copy);
	return ret;
}

static const struct file_operations dim_interval_ops = {
	.read = dim_interval_read,
	.write = dim_interval_write,
	.llseek = generic_file_llseek,
};

/*
 * baseline_init interface
 */
static ssize_t dim_baseline_init_write(struct file *file,
				       const char __user *buf,
				       size_t count, loff_t *ppos)
{
	char *buf_copy = NULL;
	int ret, val;

	if (*ppos != 0 || count > 2)
		return -EINVAL;

	buf_copy = memdup_user_nul(buf, count);
	if (IS_ERR(buf_copy))
		return PTR_ERR(buf_copy);

	if (sscanf(buf_copy, "%d", &val) != 1 || val != 1) {
		ret = -EINVAL;
		goto out;
	}

	ret = dim_measure_baseline_build();
	if (ret != 0)
		goto out;

	ret = count;

out:
	kfree(buf_copy);
	return ret;
}

static const struct file_operations dim_baseline_init_ops = {
	.write = dim_baseline_init_write,
	.llseek = generic_file_llseek,
};

/*
 * tampered_action interface
 */
static ssize_t dim_tampered_action_read(struct file *file,
				char __user *buf,
				size_t count,
				loff_t *ppos)
{
	char tmpbuf[DIM_FS_TMP_BUF_SIZE];
	ssize_t len;

	if (buf == NULL || ppos == NULL)
		return -EINVAL;

	len = scnprintf(tmpbuf, DIM_FS_TMP_BUF_SIZE, "%u\n",
		dim_measure_get_tampered_action());

	return simple_read_from_buffer(buf, count, ppos, tmpbuf, len);
}

static ssize_t dim_tampered_action_write(struct file *file,
				const char __user *buf,
				size_t count, loff_t *ppos)
{
	char *buf_copy = NULL;
	unsigned int val;
	int ret;

	if (buf == NULL || ppos == NULL || *ppos != 0 || count <= 0 || count > 2)
		return -EINVAL;

	buf_copy = memdup_user_nul(buf, count);
	if (IS_ERR(buf_copy))
		return PTR_ERR(buf_copy);

	if (sscanf(buf_copy, "%u", &val) != 1) {
		ret = -EINVAL;
		goto out;
	}

	if (val != 0 && val != 1) {
		ret = -EINVAL;
		goto out;
	}

	ret = dim_measure_set_tampered_action(val);
	if (ret < 0)
		goto out;

	ret = count;
out:
	kfree(buf_copy);
	return ret;
}

static const struct file_operations dim_tampered_action_ops = {
	.read = dim_tampered_action_read,
	.write = dim_tampered_action_write,
	.llseek = generic_file_llseek,
};

enum dim_inos {
	DIM_MEASURE, /* trigger a dim measurement */
	DIM_RUNTIME_STATUS, /* query dim status */
	DIM_ASCII_RUNTIME_MEASUREMENTS, /* query dim measure log */
	DIM_INTERVAL, /* set or query dim measurement interval */
	DIM_BASELINE_INIT, /* trigger a dim baseline reset */
	DIM_TAMPERED_ACTION, /* set or query dim measure tampered action */
	DIM_LAST,
};

struct dentry *dim_root;
static const struct tree_descr dim_files[] = {
	[DIM_MEASURE] = {"measure", &dim_measure_ops, 0600},
	[DIM_RUNTIME_STATUS] = {"runtime_status",
		&dim_runtime_status_ops, 0600},
	[DIM_ASCII_RUNTIME_MEASUREMENTS] = {"ascii_runtime_measurements",
		&dim_ascii_measurements_ops, 0600},
	[DIM_INTERVAL] = {"interval", &dim_interval_ops, 0600},
	[DIM_BASELINE_INIT] = {"baseline_init", &dim_baseline_init_ops, 0600},
	[DIM_TAMPERED_ACTION] = {"tampered_action", &dim_tampered_action_ops, 0600},
	[DIM_LAST] = {"", NULL, 0},
};

static struct dentry *dim_entry_list[DIM_LAST];

int dim_fs_init(void)
{
	int i, ret;

	dim_root = securityfs_create_file("dim", S_IFDIR | 0500, NULL, NULL, NULL);
	if (IS_ERR(dim_root)) {
		ret = PTR_ERR(dim_root);
		pr_err("fail to create dim fs root (reason: %d)\n", ret);
		return ret;
	}

	for (i = 0; i < DIM_LAST; i++) {
		dim_entry_list[i]  = securityfs_create_file(dim_files[i].name,
							    dim_files[i].mode,
							    dim_root,
							    NULL,
							    dim_files[i].ops);
		if (IS_ERR(dim_entry_list[i])) {
			ret = PTR_ERR(dim_entry_list[i]);
			pr_err("fail to create dim fs interface %s: %d\n",
				dim_files[i].name, ret);
			goto out;
		}
	}

	return 0;

out:
	for (i = DIM_LAST - 1; i >= 0; i--) {
		securityfs_remove(dim_entry_list[i]);
		dim_entry_list[i] = NULL;
	}

	securityfs_remove(dim_root);
	dim_root = NULL;
	return ret;
}

void dim_fs_destroy(void)
{
	int i;

	if (dim_root == NULL)
		return;

	for (i = DIM_LAST - 1; i >= 0; i--) {
		securityfs_remove(dim_entry_list[i]);
		dim_entry_list[i] = NULL;
	}

	securityfs_remove(dim_root);
	dim_root = NULL;
}

#if IS_MODULE(CONFIG_EULEROS_DIM_MONITOR)
EXPORT_SYMBOL(dim_root);
#endif
